/*
 * spi-test.c  - LomboTech SPI NOR Test Program
 *
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#define DEBUG

#include <linux/init.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/math64.h>
#include <linux/random.h>
#include <linux/time.h>
#include <linux/spi/spi.h>
#include <mach/lombo_spinor.h>

#include "common.h"

/* redefine mode name, for PTR_XXX */
#undef MOD_NAME
#define MOD_NAME	"SPI_TEST"

#define SECTOR_SIZE	0x10000
#define SECTOR_NUM	128
#define BUFF_SIZE	(4 * 1024)

typedef int (*test_item_t)(void);

static int read_line_num[] = {1, 2, 4};
static int write_line_num[] = {1, 4};
static struct timeval tv;
static unsigned long long start_us, end_us, us;

static u32 u32_random(u32 range)
{
	u32 rand;

	if (range == 0) {
		get_random_bytes(&rand, sizeof(u32));

		return rand;
	}

	get_random_bytes(&rand, sizeof(u32));
	rand &= (range - 1);

	return rand;
}

static long get_current_time(void)
{
	do_gettimeofday(&tv);

	return tv.tv_usec;
}

static int change_line(u8 read_line, u8 write_line)
{
	lombo_nor_change_readline(read_line);
	lombo_nor_change_writeline(write_line);

	if (read_line == 4 || write_line == 4)
		return lombo_nor_quad_enable();

	return 0;
}

static int chip_erase_test(void)
{
	int ret = 0;

	PRT_DBG("Chip erase test start...\n");

	start_us = get_current_time();
	ret = lombo_erase_chip();
	if (ret) {
		PRT_ERR("Chip erase test error.\n");
		return ret;
	}
	end_us = get_current_time();
	us = end_us - start_us;

	PRT_INFO("chip erase consume us: %lld\n", us);

	return ret;
}

static int nor_write_read_test(void)
{
	int ret = 0;
	int rand_sector_num, i;
	u32 addr;
	u_char *tx_buff = NULL;
	u_char *rx_buff = NULL;
	int buff_size = BUFF_SIZE;

	PRT_DBG("write read test start...\n");

	tx_buff = kmalloc(buff_size * sizeof(u_char), GFP_KERNEL);
	rx_buff = kmalloc(buff_size * sizeof(u_char), GFP_KERNEL);
	if (!tx_buff || !rx_buff) {
		PRT_ERR("alloc buff failed.\n");
		goto test_failed;
	}

	memset(tx_buff, 0, buff_size * sizeof(*tx_buff));
	memset(rx_buff, 0, buff_size * sizeof(*rx_buff));

	rand_sector_num = u32_random(SECTOR_NUM);
	addr = rand_sector_num * SECTOR_SIZE;

	PRT_DBG("tx_buff:0x%px\trx_buff:0x%px\taddr:0x%x\n",
		tx_buff, rx_buff, addr);

	ret = lombo_nor_erase(addr, buff_size);
	if (ret) {
		PRT_ERR("failed to erase nor\n");
		goto test_failed;
	}

	ret = lombo_nor_read(addr, buff_size, rx_buff);
	if (ret) {
		PRT_ERR("failed to read nor\n");
		goto test_failed;
	}

	for (i = 0; i < buff_size; i++)
		if (rx_buff[i] != 0xFF) {
			PRT_DBG("index:%d\tbuff data:0x%x\n", i, rx_buff[i]);
			PRT_ERR("erase data(0xFF) verify failed\n");
			goto test_failed;
	}

	for (i = 0; i < buff_size; i++)
		tx_buff[i] = (i % 256);

	PRT_DBG("len:%d addr:0x%x\n", buff_size, addr);

	start_us = get_current_time();
	ret = lombo_nor_write(addr, buff_size, tx_buff);
	if (ret) {
		PRT_ERR("failed to write nor\n");
		goto test_failed;
	}
	end_us = get_current_time();
	us = end_us - start_us;

	PRT_INFO("write: total byte: %d consume us: %lld\n", buff_size, us);

	start_us = get_current_time();
	ret = lombo_nor_read(addr, buff_size, rx_buff);
	if (ret) {
		PRT_ERR("failed to read nor\n");
		goto test_failed;
	}
	end_us = get_current_time();
	us = end_us - start_us;

	PRT_INFO("read: total byte: %d consume us: %lld\n", buff_size, us);

	PRT_DBG("waiting for data verification...\n");

	for (i = 0; i < buff_size; i++) {
		if (rx_buff[i] != tx_buff[i]) {
			PRT_ERR("data verify error\n");
			PRT_DBG("index:%d\trx_data:%d\ttx_data:%d\n",
				i, rx_buff[i], tx_buff[i]);
			goto test_failed;
		}
	}

	kfree(rx_buff);
	kfree(tx_buff);

	PRT_INFO("write and read test success\n");

	return ret;

test_failed:
	if (rx_buff)
		kfree(rx_buff);
	if (tx_buff)
		kfree(tx_buff);
	ret = 1;

	PRT_ERR("write and read test failed\n");

	return ret;
}

/* add new test case here */
static test_item_t test_item[] = {
	chip_erase_test,
	nor_write_read_test,
};

void spi_test(void)
{
	int	ret = 0;
	int	err_count = 0;
	int	i, j, k;

	PRT_INFO("---------------- SPI TEST START... ----------------\n");

	for (j = 0; j < ARRAY_SIZE(read_line_num); j++) {
		for (k = 0; k < ARRAY_SIZE(write_line_num); k++) {
			ret = change_line(read_line_num[j],
					write_line_num[k]);
			if (ret)
				PRT_ERR("change line failed\n");

			PRT_INFO("rline:%d wline:%d\n", read_line_num[j],
				write_line_num[k]);

			for (i = 0; i < ARRAY_SIZE(test_item); i++) {
				ret = test_item[i]();
				if (ret) {
					PRT_ERR("failed case index:%d\n", i);
					err_count++;
				}
			}

			if (read_line_num[j] == 4
				|| write_line_num[k] == 4) {
				ret = lombo_nor_quad_disable();
				if (ret)
					PRT_ERR("quad disable failed\n");
			}
		}
	}

	PRT_INFO("------------------- SPI TEST END -------------------\n");
	if (err_count)
		PRT_INFO("TEST RESULT: FAILED!ERR COUNTS: %d\n", err_count);
	else
		PRT_INFO("TEST RESULT: SUCCESS!\n");

	return;
}
